/* Copyright (C) 2014  Jean-Sebastien Castonguay
 *
 * This file is part of TechnoBoard.
 *
 * TechnoBoard is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * TechnoBoard is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with TechnoBoard.  If not, see <http://www.gnu.org/licenses/>. */

#include "technoboard.h"
#include "driver_pwm.h"
#include "system.h"

static const unsigned int timer2Prescaler[PWM_TIMER_NB_OF_PERIOD] =
{T2_PS_1_1, T2_PS_1_8, T2_PS_1_64, T2_PS_1_256};

static const unsigned int timer3Prescaler[PWM_TIMER_NB_OF_PERIOD] =
{T3_PS_1_1, T3_PS_1_8, T3_PS_1_64, T3_PS_1_256};


static bool ConfigTimerSource (
        PWM_Output output,
        PWM_TimerSourcePeriod timerSourcePeriod,
        unsigned int pwmPeriod) {

    switch (output) {
    case PWM_OUTPUT_P4_2:
        OpenTimer2(
                T2_ON |
                T2_IDLE_CON |
                T2_GATE_OFF |
                timer2Prescaler[timerSourcePeriod] |
                T2_32BIT_MODE_OFF |
                T2_SOURCE_INT,
                /*pwmPeriod*/0xffff);
        break;
    case PWM_OUTPUT_P4_8:
    default:
        OpenTimer3(
                T3_ON |
                T3_IDLE_CON |
                T3_GATE_OFF |
                timer3Prescaler[timerSourcePeriod] |
                T3_SOURCE_INT,
                /*pwmPeriod*/0xffff);
        break;
    }

    return true;
}


static inline unsigned int ComputeOCRValue( unsigned int dutyCycle, unsigned int pwmPeriod) {

    dutyCycle = MIN( dutyCycle, 100);
    return ((uint32_t)dutyCycle * (uint32_t)pwmPeriod) / 100;
}


/* Period Source must be configured.
 * dutyCycle in [%] from 0 to 100. */
bool PWM_Start(
        PWM_Output output,
        PWM_TimerSourcePeriod timerScrPeriod,
        unsigned int pwmPeriod,
        unsigned int dutyCycle) {

    unsigned int OCR;

    if (output >= PWM_NB_OF_OUTPUT) {
        return false;
    }

    if (timerScrPeriod >= PWM_TIMER_NB_OF_PERIOD) {
        return false;
    }

    ConfigTimerSource( output, timerScrPeriod, pwmPeriod);
    OCR = ComputeOCRValue( dutyCycle, pwmPeriod);
    
    switch (output) {
    case PWM_OUTPUT_P4_2:
    default:
        PPSOutput( PPS_RP8, PPS_OC1);
        CloseOC1();
        ConfigIntOC1( OC_INT_OFF);
        OpenOC1(OC_IDLE_CON |
                OC_TIMER2_SRC |
                //CMP_FAULT2_IN_DISABLE |
                //OC_FAULT1_IN_DISABLE |
                //OC_FAULT0_IN_DISABLE |
                //OC_PWM_FAULT_CLEAR |
                //OC_TRIG_CLEAR_SYNC |
                OC_PWM_EDGE_ALIGN,

                OC_SYNC_TRIG_IN_CURR_OC,

                pwmPeriod, OCR);
        break;
    case PWM_OUTPUT_P4_8:
        PPSOutput( PPS_RP4, PPS_OC2);
        CloseOC2();
        ConfigIntOC2( OC_INT_OFF);
        OpenOC2(OC_IDLE_CON |
                OC_TIMER3_SRC |
                //CMP_FAULT2_IN_DISABLE |
                //OC_FAULT1_IN_DISABLE |
                //OC_FAULT0_IN_DISABLE |
                //OC_PWM_FAULT_CLEAR |
                //OC_TRIG_CLEAR_SYNC |
                OC_PWM_EDGE_ALIGN,

                OC_SYNC_TRIG_IN_CURR_OC,

                pwmPeriod, OCR);
        break;
    }

    return true;
}


/* dutyCycle from 0 to 100. */
bool PWM_SetDutyCycle( PWM_Output output, unsigned int dutyCycle) {
    
    unsigned int OCR;

    if (output >= PWM_NB_OF_OUTPUT) {
        return false;
    }

    dutyCycle = MIN( dutyCycle, 100);

    switch (output) {
    case PWM_OUTPUT_P4_2:
    default:
        OCR = ComputeOCRValue( dutyCycle, ReadPeriodOC1PWM());
        SetDCOC1PWM( OCR, ReadPeriodOC1PWM());
        break;
    case PWM_OUTPUT_P4_8:
        OCR = ComputeOCRValue( dutyCycle, ReadPeriodOC2PWM());
        SetDCOC2PWM( OCR, ReadPeriodOC2PWM());
        break;
    }

    return true;
}


bool PWM_Stop( PWM_Output output) {

    if (output >= PWM_NB_OF_OUTPUT) {
        return false;
    }

    switch (output) {
    case PWM_OUTPUT_P4_2:
    default:
        PPSOutput( PPS_RP8, PPS_NULL);
        CloseOC1();
        break;
    case PWM_OUTPUT_P4_8:
        PPSOutput( PPS_RP4, PPS_NULL);
        CloseOC2();
        break;
    }

    return true;
}
